# configure-interfaces.pm
# Copyright (C) 2006  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

#
# For more information: http://carrierclass.net/
#
use strict; use warnings;

package CCNCore::Provision::Application::interfaces;
use base qw(CCNCore::Provision::Base);
use CCN::Text;
use CCNCore::Scripting;
use CCNCore::Home;

sub register
{
    my $self = shift;
    $self->register_on_table('server_interface');
    $self->register_on_table('server_network');
    $self->register_on_table('server_router');
    $self->register_on_column('server','default_router');
}

sub server_where
{
    my $self = shift;
    my ($table) = @_;

    return 'id = $server'     if $table eq 'server';
    return 'server = $server' if $table =~ /^server/;
}

sub run()
{
    my $self = shift;
    return if $self->p->force_manager;

    $self->CHECKFOR ('/sbin/ifup');
    $self->CHECKFOR ('/sbin/ifdown');

    my $done = 0;

    # Gather data on all the local interfaces.

    our @eth = ();
    our %eth = ();

    $self->sql_do_for(<<'SQL',$self->p->server_pk,
        SELECT ifname, ipnet_ip, ipnet_mask, demodhcp, mac, default_router 
        FROM server, server_interface 
        WHERE server.id = server_interface.server AND server.id = ?
          AND ipnet_ip IS NOT NULL AND ipnet_ip <> ''
SQL
    sub
    {
        my ($if,$ip,$net,$demo,$mac,$default_router) = @_;
        push @eth, $if;
        $eth{$if} = {
          DEVICE    => $if,
          BOOTPROTO => (defined $demo && $demo =~ /^t/i) ? 'dhcp' : 'none',    # Redhat version
          PROTOCOL  => (defined $demo && $demo =~ /^t/i) ? 'dhcp' : 'static',  # Debian version
          HWADDR    => CCN::Text::mac_from_dotted($mac),
          ONBOOT    => 'yes',
          IPADDR    => $ip,
          NETMASK   => $net,
          USERCTL   => 'no',
          # It's OK to not have a default gateway (small-size single-subnet system)
          GATEWAY   => $default_router || '',
        };
    });
    
    return $self->error('no Ethernet interfaces defined for this server.')
        if $#eth < 0;

    # Configure known network interfaces based on OS.

    if($self->p->operating_system =~ /^(centos|fc\d+|redhat)/)
    {
      CAT "/etc/sysconfig/network", template_text <<'EOT',
NETWORKING=yes
HOSTNAME=_{server}_
GATEWAY=_{router}_
EOT
            {
                server => $self->p->server_name,
                router => $eth{$eth[0]}->{GATEWAY},
            };

        for my $rank (1 .. $#eth+1)
        {
            my $eth = $eth[$rank-1];
            our %current;
            # Read previous configuration
            our $fn = "/etc/sysconfig/network-scripts/ifcfg-$eth";
            {
                open(my $fh,'<',$fn) or $self->warn("open $fn: $!"), next;
                while(<$fh>)
                {
                    chomp;
                    my ($k,$v) = split(/=/);
                    $current{$k} = $v;
                }
                close($fh);
            }

            # Create new configuration
            my $config = '';
            for my $k (qw( DEVICE BOOTPROTO HWADDR ONBOOT TYPE BROADCAST NETWORK NETMASK IPADDR USERCTL ))
            {
                $config .= sprintf("%s=%s\n", $k, $eth{$eth}->{$k}), next
                    if exists  $eth{$eth}->{$k};
                $config .= sprintf("%s=%s\n", $k, $current{$k}), next
                    if exists  $current{$k};
                $self->warn ("WARNING: No value for $eth $k");
            }
            CAT $fn, $config;
            IF_CHANGED $fn => "/sbin/ifdown $eth; /sbin/ifup $eth;" if $rank > 1;
        } # for my $rank
        $done = 1;
    } # OS == RedHat base

    if($self->p->operating_system =~ /^(debian|ccn)/)
    {
        $self->CHECKFOR ('/etc/network/interfaces');

        my $ifaces = '';
        my $dhclient = '';
        my $maps   = '';
        my $cmd = '';
        for my $rank (1 .. $#eth+1)
        {
            my $eth = $eth[$rank-1];
            my $ipaddr  = $eth{$eth}->{IPADDR};
            my $netmask = $eth{$eth}->{NETMASK};
            my $gateway = $eth{$eth}->{GATEWAY};
            $gateway = "gateway $gateway" if defined $gateway and $gateway ne '';
            my $hwaddr  = $eth{$eth}->{HWADDR};
            my $proto   = $eth{$eth}->{PROTOCOL};

            my $dotted = CCN::Text::mac_to_dotted($hwaddr);
            $maps .= "map ${dotted} network${rank}\n";

            if( $rank == 1 )
            {
                $ifaces .= <<"EOT";
iface network1 inet ${proto}
    address ${ipaddr}
    netmask ${netmask}
    ${gateway}

EOT
                $dhclient .= <<"EOT";
send dhcp-client-identifier $dotted;
EOT
            }
            else
            {
                $ifaces .= <<"EOT";
iface network${rank} inet ${proto}
    address ${ipaddr}
    netmask ${netmask}

EOT
                $cmd .= "/sbin/ifdown $eth; /sbin/ifup $eth";
            }
            $dhclient .= <<"EOT";
#alias {
#    interface "$eth";
#    fixed-address $ipaddr;
#    option subnet-mask $netmask;
#}
EOT
        } # for my $rank

        my $ifs = join(' ', @eth);
        my $home_manager = CCNCore::Home::manager;
        CAT '/etc/network/interfaces', <<"EOT";
# Which interfaces to start at boot
auto lo ${ifs}

# Loopback interface
iface lo inet loopback

# Maps MAC addresses to interfaces
mapping eth*
    script $home_manager/lib/CCNCore/get-mac-address.sh
    ${maps}
# Actual interfaces
${ifaces}
EOT

        IF_CHANGED '/etc/network/interfaces' => $cmd;

        # Same code is used for resolv.conf in configure_resolv.pm
        my $ns = "prepend domain-name-servers 127.0.0.1";
        $self->sql_do_for(<<'SQL', $self->p->system_pk,
            SELECT id 
            FROM server 
            WHERE the_system = ? AND dns_master = 'TRUE'
SQL
        sub
        {
            my $server_pk = shift;
            return if $server_pk eq $self->p->server_pk;
            my $ip = $self->primary_ip($server_pk);
            $ns .= ", $ip";
        });
        $ns .= ";\n";
        
        my $server_name = $self->p->server_name;
        my $system_name = $self$self->p->system_name;
        CAT '/etc/dhcp3/dhclient.conf', <<"EOT";
send host-name "$server_name";
supersede domain-name "$system_name";
$ns
$dhclient
EOT

        $done = 1;

    } # OS == Debian base

    # This is mostly used as a reminder/check.
    # If "lo" is not up, most services (including bind) will not start, creating havoc.
    IF_CHANGED '' => '/sbin/ifup lo || echo Ignoring';

    $self->sql_do_for(<<'SQL', $self->p->server_pk,
        SELECT router_ip, ipnet_ip, ipnet_mask 
        FROM server_router
        WHERE server = ?
SQL
    sub
    {
        my ($router,$net,$mask) = @_;
        RCLOCAL("route add -net $net netmask $mask gw $router") if $router ne '';
    });

    $self->error ("Could not configure Ethernet interfaces, please verify configuration.")
        unless $done;
}

1;